#ifndef STATISTICS_H
#define STATISTICS_H

#include <algorithm>
#include <initializer_list>
#include <limits>
#include <utility>
namespace NahidaProject {
	template<class T> class __declspec(dllexport) Statistics {
	public:
		using T2 = decltype(T{} *T{});

	public:
		inline Statistics();
		inline explicit Statistics(T measurement);
		inline Statistics(std::initializer_list<T> init);
		template<class InputIt> inline explicit Statistics(InputIt first, InputIt last);
		inline Statistics& Insert(const T& measurement) noexcept;
		template<class InputIt> inline Statistics& Insert(const InputIt& first, const InputIt& last) noexcept;
		inline Statistics& operator+=(const T& measurement) noexcept;
		inline Statistics& operator+=(const Statistics& rhs) noexcept;
		inline void Clear() noexcept;

		[[nodiscard]] inline size_t Count() const noexcept;
		[[nodiscard]] inline T Latest() const noexcept;
		[[nodiscard]] inline T Mean() const noexcept;
		[[nodiscard]] inline T Minimum() const noexcept;
		[[nodiscard]] inline T Maximum() const noexcept;
		[[nodiscard]] inline T Sum() const noexcept;
		[[nodiscard]] inline T2 SumOfSquares() const noexcept;
		[[nodiscard]] inline auto Variance() const noexcept;
		[[nodiscard]] inline T StandardDeviation() const noexcept;

		template<class U> friend Statistics<U> operator+(const Statistics<U>& lhs, const T& rhs) noexcept;
		template<class U> friend Statistics<U> operator+(const Statistics<U>& lhs, const Statistics<U>& rhs) noexcept;
		template<class U> friend bool operator==(const Statistics<U>& lhs, const Statistics<U>& rhs) noexcept;
		template<class U> friend bool operator!=(const Statistics<U>& lhs, const Statistics<U>& rhs) noexcept;
		template<class U> friend bool operator<(const Statistics<U>& lhs, const Statistics<U>& rhs) noexcept;
		template<class U> friend bool operator<=(const Statistics<U>& lhs, const Statistics<U>& rhs) noexcept;
		template<class U> friend bool operator>(const Statistics<U>& lhs, const Statistics<U>& rhs) noexcept;
		template<class U> friend bool operator>=(const Statistics<U>& lhs, const Statistics<U>& rhs) noexcept;

	private:
		size_t count{};
		T latest;
		T maximum;
		T minimum;
		T sum;
		T2 sumOfSquares;
	};


	template<class T>
	Statistics<T>::Statistics() : count(T{ 0 }), latest(T{ 0 }), maximum(std::numeric_limits<T>::lowest()), minimum(std::numeric_limits<T>::max()), sum(T{ 0 }), sumOfSquares(T2{ 0 }) {

	}

	template<class T> Statistics<T>::Statistics(T measurement) : count(1), latest(measurement), maximum(measurement), minimum(measurement), sum(measurement), sumOfSquares(measurement* measurement) {

	}

	template<class T>Statistics<T>::Statistics(std::initializer_list<T> init) : Statistics() {
		for (const auto& measurement : init)
			*this += measurement;
	}


	template<class T> template<class InputIt> Statistics<T>::Statistics(InputIt first, InputIt last) : Statistics() {
		for (InputIt itr = first; itr != last; ++itr)
			*this += *itr;
	}

	template<class T>
	void Statistics<T>::Clear() noexcept
	{
		*this = std::move(Statistics());
	}

	template<class T>
	size_t Statistics<T>::Count() const noexcept
	{
		return count;
	}

	template<class T>
	Statistics<T>& Statistics<T>::Insert(const T& measurement) noexcept
	{
		*this += measurement;
		return *this;
	}

	template<class T>
	template<class InputIt>
	Statistics<T>& Statistics<T>::Insert(const InputIt& first, const InputIt& last) noexcept
	{
		for (InputIt itr = first; itr != last; ++itr)
			*this += *itr;

		return *this;
	}

	template<class T>
	T Statistics<T>::Latest() const noexcept
	{
		return latest;
	}

	template<class T> T Statistics<T>::Mean() const noexcept {
		return sum / std::max<size_t>(count, 1);
	}

	template<class T> T Statistics<T>::Minimum() const noexcept {
		return minimum;
	}

	template<class T> T Statistics<T>::Maximum() const noexcept {
		return maximum;
	}

	template<class T>T Statistics<T>::Sum() const noexcept {
		return sum;
	}

	template<class T> typename Statistics<T>::T2 Statistics<T>::SumOfSquares() const noexcept {
		return sumOfSquares;
	}

	template<class T>
	auto Statistics<T>::Variance() const noexcept
	{
		auto denom = std::max<size_t>(count, 1);
		return ((sumOfSquares / denom) - (sum / denom) * (sum / denom));
	}


	template<class T>
	T Statistics<T>::StandardDeviation() const noexcept
	{
		return sqrt(Variance());
	}

	template<class T>
	Statistics<T>& Statistics<T>::operator+=(const T& measurement) noexcept
	{
		latest = measurement;

		if (latest < minimum)
			minimum = latest;
		else if (latest > maximum)
			maximum = latest;

		sum += latest;
		sumOfSquares += latest * latest;

		++count;

		return *this;
	}

	template<class T> Statistics<T>& Statistics<T>::operator+=(const Statistics& rhs) noexcept {
		count += rhs.count;
		latest = rhs.latest;
		maximum = std::max(maximum, rhs.maximum);
		minimum = std::min(minimum, rhs.minimum);
		sum += rhs.sum;
		sumOfSquares += rhs.sumOfSquares;

		return *this;
	}

	template<class T> inline Statistics<T> operator+(const Statistics<T>& lhs, const T& rhs) noexcept {
		Statistics<T> s;
		s += lhs;
		s += rhs;
		return s;
	}

	template<class T> inline Statistics<T>& operator+(const Statistics<T>& lhs, const Statistics<T>& rhs) noexcept {
		Statistics<T> s;
		s += lhs;
		s += rhs;
		return s;
	}

	template<class T> inline bool operator==(const Statistics<T>& lhs, const Statistics<T>& rhs) noexcept {
		if (lhs.count != rhs.count)
			return false;
		else if (lhs.latest != rhs.latest)
			return false;
		else if (lhs.minimum != rhs.minimum)
			return false;
		else if (lhs.maximum != rhs.maximum)
			return false;
		else if (lhs.sum != rhs.sum)
			return false;
		else
			return true;
	}

	template<class T>
	inline bool operator!=(const Statistics<T>& lhs, const Statistics<T>& rhs) noexcept
	{
		return !(lhs == rhs);
	}

	template<class T>
	inline bool operator<(const Statistics<T>& lhs, const Statistics<T>& rhs) noexcept
	{
		if (lhs.count < rhs.count)
			return true;
		else if (lhs.latest < rhs.latest)
			return true;
		else if (lhs.minimum < rhs.minimum)
			return true;
		else if (lhs.maximum < rhs.maximum)
			return true;
		else if (lhs.sum < rhs.sum)
			return true;
		else
			return false;
	}

	template<class T>
	inline bool operator<=(const Statistics<T>& lhs, const Statistics<T>& rhs) noexcept
	{
		return !(rhs < lhs);
	}

	template<class T>
	inline bool operator>(const Statistics<T>& lhs, const Statistics<T>& rhs) noexcept
	{
		return rhs < lhs;
	}

	template<class T>
	inline bool operator>=(const Statistics<T>& lhs, const Statistics<T>& rhs) noexcept
	{
		return !(lhs < rhs);
	}
}

#endif    // average_h__